package ru.ifmo.automaton.generators.custom;

import org.w3c.dom.Element;
import ru.ifmo.automaton.Automaton;
import ru.ifmo.automaton.AutomatonImp;
import ru.ifmo.common.ObjectExtractor;
import ru.ifmo.common.Source;
import ru.ifmo.common.xml.XmlCollectionExtractor;
import ru.ifmo.common.xml.XmlMapExtractor;
import ru.ifmo.common.xml.XmlObjectExtractor;
import ru.ifmo.common.xml.XmlUtil;

import java.util.*;

/**
 * @author avhaliullin
 */
public class XmlAutomatonSourceExtractor<C, S> implements XmlObjectExtractor<Source<Automaton<C, S>>> {
    private static final String TAG_STATE = "state";
    private static final String TAG_TRANSITION = "transition";
    private static final String ATTRIBUTE_SYMBOL = "symbol";
    private static final String ATTRIBUTE_STATE_LABEL = "label";
    private static final String ATTRIBUTE_STATE = "state";
    private static final String TAG_TRANSITIONS = "transitions";
    private static final String TAG_TERMINAL_STATES = "terminal-states";
    private static final String TAG_INITIAL_STATE = "initial-state";
    private static final String TAG_FAIL_STATE = "fail-state";
    private static final String TAG_ALPHABET = "alphabet";
    private static final String TAG_SYMBOL = "symbol";

    private final XmlMapExtractor<S, Map<C, S>> statesMapExtractor;


    private final ObjectExtractor<S, String> stateLabelExtractor;

    private XmlCollectionExtractor<S, Set<S>> statesSetExtractor;

    private XmlCollectionExtractor<C, List<C>> alphabetExtractor;

    public XmlAutomatonSourceExtractor(final ObjectExtractor<C, String> symbolExtractor,
                                       final ObjectExtractor<S, String> stateLabelExtractor) {
        this.stateLabelExtractor = stateLabelExtractor;

        this.statesSetExtractor =
                new XmlCollectionExtractor<S, Set<S>>(
                        new Source<Set<S>>() {
                            public Set<S> create() {
                                return new HashSet<S>();
                            }
                        },
                        TAG_STATE,
                        new XmlObjectExtractor<S>() {
                            public S extract(Element source) {
                                return stateLabelExtractor.extract(source.getAttribute(ATTRIBUTE_STATE));
                            }
                        }
                );

        XmlMapExtractor<C, S> transitionsMapExtractor = new XmlMapExtractor<C, S>(
                new Source<Map<C, S>>() {
                    public Map<C, S> create() {
                        return new HashMap<C, S>();
                    }
                },
                TAG_TRANSITION,
                ATTRIBUTE_SYMBOL,
                symbolExtractor,
                ATTRIBUTE_STATE,
                stateLabelExtractor);

        statesMapExtractor = new XmlMapExtractor<S, Map<C, S>>(
                new Source<Map<S, Map<C, S>>>() {
                    public Map<S, Map<C, S>> create() {
                        return new HashMap<S, Map<C, S>>();
                    }
                },
                TAG_STATE,
                ATTRIBUTE_STATE_LABEL,
                stateLabelExtractor,
                transitionsMapExtractor);

        alphabetExtractor = new XmlCollectionExtractor<C, List<C>>(
                new Source<List<C>>() {
                    public List<C> create() {
                        return new ArrayList<C>();
                    }
                },
                TAG_SYMBOL,
                new XmlObjectExtractor<C>() {
                    public C extract(Element source) {
                        return symbolExtractor.extract(source.getTextContent());
                    }
                }
        );
    }

    public Source<Automaton<C, S>> extract(Element element) {
        final Map<S, Map<C, S>> transitions =
                statesMapExtractor.extract(XmlUtil.getSingleElement(element, TAG_TRANSITIONS));

        final S initialState = stateLabelExtractor.extract(
                XmlUtil.getSingleElement(element, TAG_INITIAL_STATE).getAttribute(ATTRIBUTE_STATE));

        Element failStateElement = XmlUtil.getSingleElement(element, TAG_FAIL_STATE);
        S failState = null;
        if (failStateElement != null) {
            failState = stateLabelExtractor.extract(
                    failStateElement.getAttribute(ATTRIBUTE_STATE));
        }

        Set<S> terminalStates = new HashSet<S>();
        Element terminalStatesElement = XmlUtil.getSingleElement(element, TAG_TERMINAL_STATES);
        if (terminalStatesElement != null) {
            terminalStates = statesSetExtractor.extract(terminalStatesElement);
        }

        final List<C> alphabet = alphabetExtractor.extract(XmlUtil.getSingleElement(element, TAG_ALPHABET));

        final Set<S> terminalStatesFinal = terminalStates;
        final S failStateFinal = failState;
        return new Source<Automaton<C, S>>() {
            public Automaton<C, S> create() {
                return new AutomatonImp<C, S>(transitions, terminalStatesFinal, initialState, failStateFinal, alphabet);
            }
        };
    }
}
